/*
 * DirectoryTraverser.cs
 * 
 * Depth- and breadth-first directory traversal, with wildcard and exclusion-list support.
 * Traversal includes hidden files and directories.  Visitor pattern: files and directories 
 * are "returned" via repeated invocations to the specified delegates (callback functions).
 */
using System;
using System.IO;
using System.Collections.Generic;

namespace Jitsu.IO
{
    internal class DirectoryTraverser
    {
        private DirectoryVisitor onDirectory;
        private FileVisitor onFile;

        private string[] excludeFiles;
        private string[] excludeDirectories;

        //
        // Initialization

        public DirectoryTraverser(
            DirectoryVisitor onDirectory, FileVisitor onFile,
            string[] excludeFiles, string[] excludeDirectories)
        {
            this.excludeDirectories = excludeDirectories;
            this.excludeFiles = excludeFiles;

            this.onDirectory = onDirectory;
            this.onFile = onFile;
        }

        public DirectoryTraverser(
            DirectoryVisitor onDirectory, FileVisitor onFile) :
            this(onDirectory, onFile, null, null) { }

        //
        // Interface

        public delegate void DirectoryVisitor(string relpath);
        public delegate void FileVisitor(string relpath);

        public void TraverseBreadthFirst(string baseDirectory, string filePattern, string directoryPattern)
        {
            try
            {
                // Visit this branch.
                if (onDirectory != null)
                    onDirectory(baseDirectory);

                // Breadth-first: enumerate the leaves (files) before recursing.
                if (onFile != null)
                {
                    // First, get a mutable collection of all files.
                    List<string> allfiles = new List<string>(
                        Directory.GetFiles(baseDirectory, filePattern));

                    // Remove files which match patterns in the exclusion-list.
                    if (excludeFiles != null)
                    {
                        foreach (string badpatt in excludeFiles)
                        {
                            string[] badfiles = Directory.GetFiles(baseDirectory, badpatt);
                            foreach (string bf in badfiles) allfiles.Remove(bf);
                        }
                    }

                    // Visit the surviving leaves.
                    foreach (string file in allfiles)
                        onFile(file);
                }

                // Get a mutable collection of all subdirectories.
                List<string> subdirs = new List<string>(
                    Directory.GetDirectories(baseDirectory, directoryPattern));

                // Remove subdirectories which match patterns in the exclusion-list.
                if (excludeDirectories != null)
                {
                    foreach (string badpatt in excludeDirectories)
                    {
                        string[] baddirs = Directory.GetDirectories(baseDirectory, badpatt);
                        foreach (string bd in baddirs) subdirs.Remove(bd);
                    }
                }

                // Recurse the surviving branches.
                foreach (string subdir in subdirs)
                    TraverseBreadthFirst(subdir, filePattern, directoryPattern);
            }
            catch (UnauthorizedAccessException ex)
            {
                SafeConsoleColorSetter.SetColor(ConsoleColor.Yellow);
                Console.Error.WriteLine(ex.Message);
                SafeConsoleColorSetter.ResetColor();
            }
        }
        public void TraverseBreadthFirst(string baseDirectory, string filePattern)
        { TraverseBreadthFirst(baseDirectory, filePattern, "*"); }
        public void TraverseBreadthFirst(string baseDirectory)
        { TraverseBreadthFirst(baseDirectory, "*", "*"); }

        public void TraverseDepthFirst(string baseDirectory, string filePattern, string directoryPattern)
        {
            try
            {
                // Visit this branch.
                if (onDirectory != null)
                    onDirectory(baseDirectory);

                // Depth-first: get a mutable collection of all subdirectories.
                List<string> subdirs = new List<string>(
                    Directory.GetDirectories(baseDirectory, directoryPattern));

                // Remove subdirectories which match patterns in the exclusion-list.
                foreach (string badpatt in excludeDirectories)
                {
                    string[] baddirs = Directory.GetDirectories(baseDirectory, badpatt);
                    foreach (string bd in baddirs) subdirs.Remove(bd);
                }

                // Recurse the surviving branches.
                foreach (string subdir in subdirs)
                    TraverseDepthFirst(subdir, filePattern, directoryPattern);

                // Lastly, enumerate leaves on this branch.
                if (onFile != null)
                {
                    // First, get a mutable collection of all files.
                    List<string> allfiles = new List<string>(
                        Directory.GetFiles(baseDirectory, filePattern));

                    // Remove files which match patterns in the exclusion-list.
                    if (excludeFiles != null)
                    {
                        foreach (string badpatt in excludeFiles)
                        {
                            string[] badfiles = Directory.GetFiles(baseDirectory, badpatt);
                            foreach (string bf in badfiles) allfiles.Remove(bf);
                        }
                    }

                    // Visit the surviving leaves.
                    foreach (string file in allfiles)
                        onFile(file);
                }
            }
            catch (UnauthorizedAccessException ex)
            {
                SafeConsoleColorSetter.SetColor(ConsoleColor.Yellow);
                Console.Error.WriteLine(ex.Message);
                SafeConsoleColorSetter.ResetColor();
            }
        }
        public void TraverseDepthFirst(string baseDirectory, string filePattern)
        { TraverseDepthFirst(baseDirectory, filePattern, "*"); }
        public void TraverseDepthFirst(string baseDirectory)
        { TraverseDepthFirst(baseDirectory, "*", "*"); }

    }
}